# -*- coding: utf-8 -*-
#
# Copyright (c) 2012, Rafael Pivato
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#     * Neither the name of Rafael Pivato nor the names of its contributors
#       may be used to endorse or promote products derived from this
#       software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL RAFAEL PIVATO BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import jinja2
import logging
import os
import urllib
import webapp2
import webob.exc

from google.appengine.api import app_identity
from google.appengine.api import users
from webapp2_extras import sessions
from wowgae.model import Account


class RoleForbiddenError(webob.exc.HTTPForbidden):
    """Raised when the account doesn't have the right role for a task."""

    title = 'Role Forbidden'
    explanation = (
        'Access was denied to this resource because your role\r\n'
        'does not allow you to.\r\n')


class ActivationRequiredError(webob.exc.HTTPUnauthorized):
    """Raised when the account was not activated yet."""

    title = 'Unauthorized Activation'
    explanation = (
        'This server could not verify that you are authorized\r\n'
        'to access the resource because you either did not\r\n'
        'start or finish your activation process.\r\n')


class LoginRequiredError(webob.exc.HTTPUnauthorized):
    """Raised when the user needs to be logged in before continuing."""

    title = 'Unauthorized Login'


class WebHandler(webapp2.RequestHandler):
    """Base RequestHandler for our application."""

    def initialize(self, request, response):
        """Initializes this request handler."""
        webapp2.RequestHandler.initialize(self, request, response)
        self.template = TemplateHelper(self)
        self.login = LoginInformation(self)
        welcome_name = None
        if self.current_user:
            if self.current_account:
                welcome_name = self.current_account.name
            else:
                welcome_name = self.current_user.email()
        login_url = users.create_login_url(self.request.url)
        logout_url = users.create_logout_url(self.request.url)
        request_url = self.request.url
        request_path = self.request.path
        self.template.update(application_name='WoW GAE',
                             welcome_name=welcome_name,
                             login_url=login_url,
                             logout_url=logout_url,
                             request_url=request_url,
                             request_path=request_path,
                             account=self.current_account)
        self.__update_links()
        self.__update_user_data()
        self.__update_request_data()

    def __update_links(self):
        activation = webapp2.uri_for('activation', self.request)
        wiki = webapp2.uri_for('wiki-home', self.request)
        home = webapp2.uri_for('home', self.request)
        shortener = webapp2.uri_for('shortener-create', self.request)
        share_query = urllib.urlencode([('url', self.request.url)])
        share = shortener + '?' + share_query
        reports = webapp2.uri_for('reports', self.request)
        data = {'activation': activation, 'wiki': wiki, 'home': home,
                'reports': reports, 'shortener': shortener, 'share': share}
        self.template.update(links=data)

    def __update_request_data(self):
        data = {
            'path': self.request.path,
            'url': self.request.url,
        }
        self.template.update(request=data)

    def __update_user_data(self):
        data = {}
        if self.current_user:
            data['email'] = self.current_user.email()
            data['id'] = self.current_user.user_id()
            if self.current_account:
                data['welcome'] = self.current_account.name
                data['account'] = {
                    'name': self.current_account.name,
                    'role': self.current_account.role,
                }
            else:
                data['welcome'] = self.current_user.email()
        self.template.update(user=data)

    def dispatch(self):
        """Overloads the dispatch method for request initialization."""
        self.session_store = sessions.get_store()
        try:
            webapp2.RequestHandler.dispatch(self)
        finally:
            self.session_store.save_sessions(self.response)

    def replace_browser_url(self, url):
        self.template.update(replace_url=url)

    def handle_exception(self, exception, debug):
        self.template.use('error', 'general')
        if isinstance(exception, webapp2.HTTPException):
            self.response.set_status(exception.code)
            if exception.code in [401, 403]:
                logging.warn('Access permission error: %s' % (exception.title))
                self.template.use('error', 'forbidden')
            else:
                logging.exception(exception)
            self.response.set_status(exception.code)
        else:
            self.response.set_status(500)
            logging.exception(exception)
        self.template.write()

    @webapp2.cached_property
    def current_user(self):
        """Returns the current user for this request."""
        return users.get_current_user()

    @webapp2.cached_property
    def session(self):
        """A dictionary-like session object."""
        return self.session_store.get_session(backend="memcache")

    @webapp2.cached_property
    def current_account(self):
        """Returns the current model.Account for this request."""
        current_user = self.current_user
        if current_user is None:
            return None
        else:
            user_id = current_user.user_id()
            return Account.get_by_user_id(user_id)


class TemplateHelper(object):
    """Helps dealing with a template for a RequestHandler."""

    def __init__(self, handler):
        if not isinstance(handler, WebHandler):
            webapp2.abort(500)
        self.handler = handler
        self.values = {}
        self.template = None

    def use(self, *args):
        pathname = '/'.join(args)
        filename = pathname + '.html'
        self.template = jinja_environment.get_template(filename)

    def update(self, *args, **kwds):
        for arg in args:
            self.values.update(arg)
        self.values.update(**kwds)

    def write(self):
        rendered = self.template.render(self.values)
        self.handler.response.out.write(rendered)


class LoginInformation(object):
    """Holds login information related to a request."""

    def __init__(self, handler):
        if not isinstance(handler, WebHandler):
            webapp2.abort(500)
        self.handler = handler

    @webapp2.cached_property
    def user(self):
        """Returns the current user for this request."""
        return users.get_current_user()

    @webapp2.cached_property
    def account(self):
        """Returns the current model.Account for this request."""
        if self.user is None:
            return None
        else:
            user_id = self.user.user_id()
            return Account.get_by_user_id(user_id)

    def enforce_account_role(self, required, allow_public=False):
        """Throws a ForbiddenError if the account role is lower."""
        if required == 'public' and not allow_public:
            required = 'member'
        if required != 'public':
            if self.user:
                if self.account:
                    comparison = Account.cmp_roles(self.account.role, required)
                    if comparison < 0:
                        raise RoleForbiddenError(self.account.role, required,
                                                 self.handler.request.url)
                else:
                    raise ActivationRequiredError(self.handler.request.url)
            else:
                raise LoginRequiredError(self.handler.request.url)


def _configure_environment():
    """Returns `jinja2.Environment` suitable for our application."""
    module_path = os.path.dirname(__file__)
    user_path = os.path.join(module_path, '..', '..', 'template')
    user_path = os.path.abspath(user_path)
    system_path = os.path.join(module_path, '..', 'template')
    system_path = os.path.abspath(system_path)
    if app_identity.get_application_id() == 'wowgae':
        load_order = system_path
    else:
        load_order = [user_path, system_path]
    file_loader = jinja2.FileSystemLoader(load_order)
    return jinja2.Environment(loader=file_loader)


jinja_environment = _configure_environment()

webapp2_config = {}
webapp2_config['webapp2_extras.sessions'] = {
    'secret_key': 'lZRUvWcD8MeRci6Ezl8uRQ185zTU8vuj',
    'cookie_name': 'WGSESSION'
}
